package org.fin.framework.ioc.annotation;

import org.fin.framework.ioc.exception.AnnotationScanException;
import org.fin.framework.ioc.exception.NotSupportedException;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * 注解包扫描器
 */
public class AnnotationScanner {

  /** 扫描的包路径 */
  private final String packageName;


  public AnnotationScanner(String packageName) {
    this.packageName = packageName;
  }

  /**
   * 扫描包，获取标识了指定注解的字节码对象集
   * @param annotationCls   指定注解，获取标识了这些注解的class对象
   */
  public Set<Class<?>> scan(Class<?>[] annotationCls) throws IOException {
    Set<Class<?>> result = new LinkedHashSet<>(16);

    String packageDir = packageName.replace('.', '/');
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

    Enumeration<URL> resourceUrls = classLoader.getResources(packageDir);
    while (resourceUrls.hasMoreElements()) {
      URL url = resourceUrls.nextElement();
      String protocol = url.getProtocol();

      if (protocol.equals("file")) {
        String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
        result = findAllClassesWithAnnotations(packageName, filePath, annotationCls);
      }

      else {
        throw new NotSupportedException("暂不支持" + protocol + "形式的包扫描");
      }

    }

    return result;

  }

  /**
   * 获取指定包路径中，在类上标识了指定注解的class对象，如果不注定注解，则获取全部class对象
   * @param packageName   包名
   * @param packageDir    包的绝对路径
   */
  @SuppressWarnings({"ConstantConditions", "unchecked"})
  private Set<Class<?>> findAllClassesWithAnnotations(String packageName, String packageDir, Class<?>[] annotationCls) {
    Set<Class<?>> result = new LinkedHashSet<>(16);
    File dir = new File(packageDir);

    if (!dir.exists() || !dir.isDirectory()) {
      throw new AnnotationScanException("包路径不存在或有误: " + packageName);
    }

    File[] listFiles = dir.listFiles(file -> file.isDirectory() || (file.getName().endsWith(".class")));

    for (File file : listFiles) {
      if (file.isDirectory()) {
        String filePath = file.getAbsolutePath();
        String subPackageName = packageName + "." + file.getName();

        result.addAll(findAllClassesWithAnnotations(subPackageName, filePath, annotationCls));
      }

      else {
        // 去除.class，获得类名
        String className = file.getName().substring(0, file.getName().length() - 6);
        // 拼接包名，获取全限定类名
        String qualifiedClassName = packageName + "." + className;

        try {
          Class<?> cls = Class.forName(qualifiedClassName);

          if (annotationCls == null || annotationCls.length == 0) {
            return result;
          }else {
            for (Class<?> ann : annotationCls) {

              if (cls.isAnnotationPresent((Class<? extends Annotation>) ann)) {
                result.add(cls);
                break;
              }
            }
          }


        } catch (ClassNotFoundException e) {
          // 这个错不应该会发生
          e.printStackTrace();
        }
      }
    }

    return result;
  }

  public String getPackageName() {
    return packageName;
  }
}
